package org.zoomdev.zoom.dao.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.common.Destroyable;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.io.Io;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.dao.*;
import org.zoomdev.zoom.dao.adapters.StatementAdapter;
import org.zoomdev.zoom.dao.alias.NameAdapter;
import org.zoomdev.zoom.dao.alias.NameMappingFactory;
import org.zoomdev.zoom.dao.alias.impl.DetectPrefixNameMappingFactory;
import org.zoomdev.zoom.dao.alias.impl.ToLowerCaseNameAdapter;
import org.zoomdev.zoom.dao.datasource.SingleConnectionProvider;
import org.zoomdev.zoom.dao.ddl.DatabaseBuilder;
import org.zoomdev.zoom.dao.driver.DbStructFactory;
import org.zoomdev.zoom.dao.driver.SqlDriver;
import org.zoomdev.zoom.dao.driver.h2.H2DbStruct;
import org.zoomdev.zoom.dao.driver.h2.H2Driver;
import org.zoomdev.zoom.dao.driver.mysql.MysqlDbStruct;
import org.zoomdev.zoom.dao.driver.mysql.MysqlDriver;
import org.zoomdev.zoom.dao.meta.ColumnMeta;
import org.zoomdev.zoom.dao.meta.TableMeta;
import org.zoomdev.zoom.dao.modules.CoreDaoModule;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

/**
 * dao
 *
 * @author jzoom
 */
public final class ZoomDao implements Dao, Destroyable {

    private static final Logger log = LoggerFactory.getLogger(CoreDaoModule.NAME);
    private static final NameMappingFactory NAME_MAPPING_FACTORY = DetectPrefixNameMappingFactory.DEFAULT;

    private final ConnectionProvider dataSource;
    private final CachedEntityFactory entityFactory;
    private final ThreadLocal<Ar> arHolder = new ThreadLocal<>();
    private final ThreadLocal<EAr<?>> earHolder = new ThreadLocal<>();
    private SqlDriver sqlDriver;
    private DbStructFactory dbStructFactory;
    private NameAdapter nameAdapter;

    private String url;
    private boolean output;
    private String productName;
    /**
     * 是否允许数据库结构缓存，默认允许
     */
    private boolean enableStructCache = true;

    public ZoomDao(DataSource dataSource) {
        this(new SingleConnectionProvider(dataSource));
    }

    public ZoomDao(ConnectionProvider dataSource) {
        this(dataSource, true);
    }

    /**
     * 创建一个Dao对象
     *
     * @param dataSource
     * @param lazyLoad   是否在需要使用的时候才创建各种相关对象：如绑定实体类等,改成true，启动时间将缩减500ms左右！
     */
    public ZoomDao(ConnectionProvider dataSource, boolean lazyLoad) {
        this.dataSource = dataSource;
        Db.register(this);
        entityFactory = new CachedEntityFactory(new BeanEntityFactory(this), new RecordEntityFactory(this));
        this.nameAdapter = ToLowerCaseNameAdapter.DEFAULT;
        if (lazyLoad) {
            return;
        }
        load();
    }

    private void load() {
        // 需要绑定entities
        try (Connection connection = dataSource.getConnection()) {
            DatabaseMetaData metaData = connection.getMetaData();
            String name = metaData.getDatabaseProductName();
            log.info(String.format("Database type:%s", name));
            this.url = metaData.getURL();
            this.sqlDriver = createDriver(name);
            String tableCat = sqlDriver.getTableCatFromUrl(url);
            this.productName = metaData.getDatabaseProductName();
            this.dbStructFactory = createDbStructFactory(productName, tableCat);
            if (enableStructCache) {
                this.dbStructFactory = new CachedDbStructFactory(this.dbStructFactory);
            }
        } catch (SQLException e) {
            throw new DaoException("创建Dao失败,连接数据库错误", e);
        }
    }

    private SqlDriver createDriver(String productName) {
        if (Databases.MYSQL.equals(productName)) {
            return new MysqlDriver();
        }

        if (Databases.H2.equals(productName)) {
            return new H2Driver();
        }

//        if (Databases.ORACLE.equalsIgnoreCase(productName)) {
//            return new OracleDriver();
//        }

        throw new ZoomException(String.format("不支持的数据库产品:%s", productName));
    }    public String getProductName() {
        if (productName == null) {
            lazyLoad();
        }
        return productName;
    }

    private DbStructFactory createDbStructFactory(String productName, String tableCat) {
        if (Databases.MYSQL.equals(productName)) {
            return new MysqlDbStruct(this, tableCat);
        }

        if (Databases.H2.equals(productName)) {
            return new H2DbStruct(this, tableCat);
        }

//        if (Databases.ORACLE.equalsIgnoreCase(productName)) {
//            return new OracleDbStruct(this, tableCat);
//        }

        throw new ZoomException(String.format("不支持的数据库产品:%s", productName));
    }

    @Override
    public void destroy() {
        log.info("==============Dao destroy==============");
        Classes.destroy(dbStructFactory);
        Classes.destroy(entityFactory);

        Io.closeAny(dataSource);
    }    @Override
    public EntityFactory entityFactory() {
        return this.entityFactory;
    }

    public boolean isEnableStructCache() {
        return enableStructCache;
    }

    public void setEnableStructCache(boolean enableStructCache) {
        this.enableStructCache = enableStructCache;
    }    public void release() {
        arHolder.remove();
        earHolder.remove();
    }

    @Override
    public StatementAdapter getStatementAdapter(Class<?> fieldType, Class<?> columnType) {
        StatementAdapter adapter = sqlDriver.getStatementAdapter(fieldType, columnType);
        if (adapter != null) {
            return adapter;
        }
        return StatementAdapters.create(fieldType, columnType);
    }

    private String[] getColumnNames(TableMeta meta) {

        String[] names = new String[meta.getColumns().length];
        int index = 0;
        for (ColumnMeta columnMeta : meta.getColumns()) {
            names[index++] = columnMeta.getName();
        }
        return names;
    }    @Override
    public void setNameAdapter(NameAdapter nameAdapter) {
        this.nameAdapter = nameAdapter;
    }



    @Override
    public boolean isOutput() {
        return output;
    }



    @Override
    public void setOutput(boolean output) {
        this.output = output;
    }





    public <T> T execute(ConnectionExecutor executor) {
        return ar().execute(executor);
    }

    @Override
    public String getURL() {
        return url;
    }

    public DbStructFactory getDbStructFactory() {
        lazyLoad();
        return dbStructFactory;
    }

    @Override
    public Entity getEntity(Class<?> type) {
        return entityFactory.getEntity(type);
    }

    @Override
    public Entity getEntity(String... tables) {
        return entityFactory.getEntity(tables);
    }


    public Ar ar() {
        Ar ar = arHolder.get();
        if (ar == null) {
            ar = createAr();
            arHolder.set(ar);
        }
        return ar;
    }

    @Override
    public <T> EAr<T> ar(Class<T> type) {
        EAr<T> ar = (EAr<T>) earHolder.get();
        Entity entity = entityFactory.getEntity(type);
        if (ar == null) {
            lazyLoad();
            ar = new EntityActiveRecord<T>(this, entity);
            earHolder.set(ar);
        } else {
            ar.setEntity(entity);
        }
        return ar;
    }

    @Override
    public <T> EAr<T> ar(Class<T> type, String table) {
        EAr<T> ar = (EAr<T>) earHolder.get();
        Entity entity = entityFactory.getEntity(type, table);
        if (ar == null) {
            lazyLoad();
            ar = new EntityActiveRecord<T>(this, entity);
            earHolder.set(ar);
        } else {
            ar.setEntity(entity);
        }
        return ar;
    }

    @Override
    public <T> EAr<T> ar(Entity<T> entity) {
        EAr<T> ar = (EAr<T>) earHolder.get();
        if (ar == null) {
            lazyLoad();
            ar = new EntityActiveRecord<>(this, entity);
            earHolder.set(ar);
        } else {
            ar.setEntity(entity);
        }
        return ar;
    }

    @Override
    public EAr<Record> ar(String... tables) {
        EAr<Record> ar = (EAr<Record>) earHolder.get();
        Entity entity = entityFactory.getEntity(tables);
        if (ar == null) {
            lazyLoad();
            ar = new EntityActiveRecord<>(this, entity);
            earHolder.set(ar);
        } else {
            ar.setEntity(entity);
        }
        return ar;
    }

    @Override
    public Ar table(String table) {
        return ar().table(table);
    }


    @Override
    public void clearCache() {
        dbStructFactory.clearCache();
        entityFactory.clearCache();
    }

    @Override
    public SqlDriver getDriver() {
        if (sqlDriver == null) {
            lazyLoad();
        }
        return sqlDriver;
    }

    @Override
    public DataSource getDataSource() {
        return dataSource.getDataSource();
    }

    @Override
    public NameMappingFactory getAliasPolicyFactory() {
        return NAME_MAPPING_FACTORY;
    }

    @Override
    public DatabaseBuilder builder() {
        lazyLoad();
        return new ZoomDatabaseBuilder(this);
    }


    private void lazyLoad() {
        if (sqlDriver == null) {
            synchronized (this) {
                if (sqlDriver == null) {
                    load();
                }
            }
        }
    }

    private Ar createAr() {
        lazyLoad();
        return new ActiveRecord(this, nameAdapter, output);
    }


}
